/* cgisvr.c : dgnsc cgi server
 * Copyright (C) 2009-2011 drangon <drangon.zhou@gmail.com>
 * 2011-03
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dgnsc/cgisvr.h>

#include <dgnsc/common.h>
#include <dgnsc/thread.h>
#include <dgnsc/socket.h>

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>

#define NET_TIMEOUT_MS		(100)

//////////////////////////////////////////////
////	<1> strkv_t

struct strkv_node_t
{
	char * name;
	char * value;
	struct strkv_node_t * next;
};

struct strkv_t
{
	struct strkv_node_t * root;
};

static struct strkv_t * strkv_create()
{
	struct strkv_t * skv = (struct strkv_t *)malloc( sizeof(*skv) );
	skv->root = NULL;
	return skv;
}

#if 0
static int strkv_add( struct strkv_t * skv, const char * name, const char * value )
{
	ASSERT( name != NULL && value != NULL );
	struct strkv_node_t * node = (struct strkv_node_t *)malloc( sizeof(*node) );
	node->name = strdup( name );
	node->value = strdup( value );
	node->next = skv->root;
	skv->root = node;
	return 0;
}
#endif

static int strkv_add_ex( struct strkv_t * skv, const char * name, int name_len, const char * value, int value_len )
{
	ASSERT( name != NULL && name_len >= 0 && value != NULL && value_len >= 0 );
	struct strkv_node_t * node = (struct strkv_node_t *)malloc( sizeof(*node) );
	node->name = (char *)malloc( name_len + 1 );
	memcpy( node->name, name, name_len );
	node->name[ name_len ] = '\0';
	node->value = (char *)malloc( value_len + 1 );
	memcpy( node->value, value, value_len );
	node->value[ value_len ] = '\0';
	node->next = skv->root;
	skv->root = node;
	return 0;
}

#if 0
static int strkv_del( struct strkv_t * skv, const char * name )
{
	struct strkv_node_t * parent = NULL;
	struct strkv_node_t * node = NULL;
	for( node = skv->root; node != NULL; parent = node, node = node->next ) {
		if( strcmp( node->name, name ) == 0 ) {
			if( parent == NULL ) {
				skv->root = node->next;
			}
			else {
				parent->next = node->next;
			}
			free( node->name );
			free( node->value );
			free( node );
			return 0;
		}
	}
	return -1;
}
#endif

static const char * strkv_find( struct strkv_t * skv, const char * name )
{
	struct strkv_node_t * node = NULL;
	for( node = skv->root; node != NULL; node = node->next ) {
		if( strcmp( node->name, name ) == 0 )
			return node->value;
	}
	return NULL;
}

static void strkv_destroy( struct strkv_t * skv )
{
	struct strkv_node_t * node = NULL;
	while( skv->root != NULL ) {
		node = skv->root;
		skv->root = skv->root->next;
		free( node->name );
		free( node->value );
		free( node );
	}
	free( skv );
	return;
}

static int parse_cookie( struct strkv_t * skv, const char * cookie )
{
	const char * p = cookie;
	const char * name = NULL;
	int n, nlen;
	p += strspn( p, " \t\r\n" );
	while( *p != '\0' ){
		if( *p == '$' ) {
			p += strcspn( p, ";" );
			p += 1; // skip ';'
			continue;
		}
		n = strcspn( p, "=" );
		if( n == 0 || *(p + n) != '=' )
			break;
		name = p;
		nlen = n;
		p += n + 1; // skip to '='
		n = strcspn( p, ";" );
		strkv_add_ex( skv, name, nlen, p, n );
		p += n + 1; // skip to ';'
		p += strspn( p, " \t\r\n" );
	}
	return 0;
}

static int parse_param( struct strkv_t * skv, const char * param )
{
	const char * p = param;
	const char * name = NULL;
	int n, nlen;
	p += strspn( p, " \t\r\n" );
	while( *p != '\0' ){
		n = strcspn( p, "=" );
		if( n == 0 || *(p + n) != '=' )
			break;
		name = p;
		nlen = n;
		p += n + 1; // skip to '='
		n = strcspn( p, "&" );
		strkv_add_ex( skv, name, nlen, p, n );
		p += n + 1; // skip to ';'
		p += strspn( p, " \t\r\n" );
	}
	return 0;
}

///////////////////////////////////////////////////////
////	<2> cgi session

#define REPLY_BUF_LEN	(32 * 1024 - 1)
struct cgi_t;

typedef int (*cgi_reply_func_t)( struct cgi_t * cgi, const char * data, int len );

struct cgi_t
{
	// request info
	struct strkv_t * envs;
	struct strkv_t * cookies;
	struct strkv_t * params;
	char * body;
	int body_len;
	int body_buflen;

	// reply info
	dgn_sock_t clisock;
	cgi_reply_func_t reply_func;
	char * reply_buf;
	int is_reply_body; // 0 : not body, need \r\n; 1 : in body

	// special info
	int reqid; // fastcgi only
};

static struct cgi_t * cgi_create()
{
	struct cgi_t * c = (struct cgi_t *)malloc( sizeof(*c) );
	c->envs = strkv_create();
	c->cookies = strkv_create();
	c->params = strkv_create();
	c->body = NULL;
	c->body_len = 0;
	c->body_buflen = 0;

	c->clisock = NULL;
	c->reply_func = NULL;
	c->reply_buf = (char *)malloc( REPLY_BUF_LEN + 1 );
	c->reply_buf[REPLY_BUF_LEN] = '\0';
	c->is_reply_body = 0;

	c->reqid = 0;

	return c;
}

static void cgi_destroy( struct cgi_t * c )
{
	strkv_destroy( c->envs );
	strkv_destroy( c->cookies );
	strkv_destroy( c->params );
	free( c->body ), c->body = NULL;
	free( c->reply_buf ), c->reply_buf = NULL;
	free( c );
	return;
}

static int cgi_add_env( struct cgi_t * c, const char * name, int namelen, const char * value, int valuelen )
{
	strkv_add_ex( c->envs, name, namelen, value, valuelen );
	if( namelen == 11 && strncmp( name, "HTTP_COOKIE", 11 ) == 0 ) {
		const char * cookie = strkv_find( c->envs, "HTTP_COOKIE" );
		parse_cookie( c->cookies, cookie );
	}
	else if( namelen == 12 && strncmp( name, "QUERY_STRING", 12 ) == 0 ) {
		const char * param = strkv_find( c->envs, "QUERY_STRING" );
		parse_param( c->params, param );
	}
	return 0;
}

const char * dgn_cgi_get_env( dgn_cgi_t cgi, const char * header )
{
	struct cgi_t * c = cgi;
	return strkv_find( c->envs, header );
}

int dgn_cgi_get_body_len( dgn_cgi_t cgi )
{
	struct cgi_t * c = cgi;
	return c->body_len;
}

const char * dgn_cgi_get_body( dgn_cgi_t cgi )
{
	struct cgi_t * c = cgi;
	return c->body;
}

const char * dgn_cgi_get_script_name( dgn_cgi_t cgi )
{
	return dgn_cgi_get_env( cgi, "SCRIPT_NAME" );
}

const char * dgn_cgi_get_query_string( dgn_cgi_t cgi )
{
	return dgn_cgi_get_env( cgi, "QUERY_STRING" );
}

const char * dgn_cgi_get_cookie( dgn_cgi_t cgi, const char * name )
{
	struct cgi_t * c = cgi;
	return strkv_find( c->cookies, name );
}

const char * dgn_cgi_get_param( dgn_cgi_t cgi, const char * name )
{
	struct cgi_t * c = cgi;
	return strkv_find( c->params, name );
}

int dgn_cgi_reply_header( dgn_cgi_t cgi, const char * name, const char * value )
{
	struct cgi_t * c = cgi;
	snprintf( c->reply_buf, REPLY_BUF_LEN, "%s: %s\r\n", name, value );
	return (* c->reply_func)( c, c->reply_buf, strlen(c->reply_buf) );
}

int dgn_cgi_reply_cookie( dgn_cgi_t cgi, const char * name, const char * value, int age_sec )
{
	struct cgi_t * c = cgi;
	char expires_buf[64];
	if( age_sec >= 0 ) {
		int len = 0;
		time_t now;
		struct tm nowtm;
		now = time(NULL);
		now += age_sec;
		gmtime_r( &now, &nowtm );
		len = snprintf( expires_buf, sizeof(expires_buf), ";Expires=" );
		strftime( expires_buf + len, sizeof(expires_buf) - len, "%a, %d-%b-%Y %H:%M:%S GMT", &nowtm );
	}
	else {
		expires_buf[0] = '\0';  // no Expires, valid untile browser closed
	}
	snprintf( c->reply_buf, REPLY_BUF_LEN, "Set-Cookie: %s=%s%s;Path=/", name, value, expires_buf ); 
	return (* c->reply_func)( c, c->reply_buf, strlen(c->reply_buf) );
}

int dgn_cgi_reply_status( dgn_cgi_t cgi, int code, const char * reason )
{
	struct cgi_t * c = cgi;
	snprintf( c->reply_buf, REPLY_BUF_LEN, "Status: %d %s\r\n", code, reason );
	return (* c->reply_func)( c, c->reply_buf, strlen(c->reply_buf) );
}

int dgn_cgi_reply_content_type( dgn_cgi_t cgi, const char * content_type )
{
	struct cgi_t * c = cgi;
	snprintf( c->reply_buf, REPLY_BUF_LEN, "Content-Type: %s\r\n", content_type );
	return (* c->reply_func)( c, c->reply_buf, strlen(c->reply_buf) );
}

int dgn_cgi_reply_location( dgn_cgi_t cgi, const char * location )
{
	struct cgi_t * c = cgi;
	snprintf( c->reply_buf, REPLY_BUF_LEN, "Location: %s\r\n", location );
	return (* c->reply_func)( c, c->reply_buf, strlen(c->reply_buf) );
}

int dgn_cgi_reply_body( dgn_cgi_t cgi, const char * body, int len )
{
	struct cgi_t * c = cgi;
	if( c->is_reply_body == 0 ) {
		(* c->reply_func)( cgi, "\r\n", 2 );
		c->is_reply_body = 1;
	}
	return (* c->reply_func)( c, body, len );
}

int dgn_cgi_reply_body_fmt( dgn_cgi_t cgi, const char * fmt, ... )
{
	struct cgi_t * c = cgi;
	if( c->is_reply_body == 0 ) {
		(* c->reply_func)( cgi, "\r\n", 2 );
		c->is_reply_body = 1;
	}
	va_list ap;
	va_start( ap, fmt );
	int len = vsnprintf( c->reply_buf, REPLY_BUF_LEN, fmt, ap );
	va_end( ap );
	if( len < 0 ) {
		return -1;
	}
	if( len > REPLY_BUF_LEN )
		len = REPLY_BUF_LEN;
	return (* c->reply_func)( c, c->reply_buf, len );
}

int dgn_cgi_reply( dgn_cgi_t cgi, const char * data, int len )
{
	struct cgi_t * c = cgi;
	return (* c->reply_func)( c, data, len );
}

//////////////////////////////////////////
////	<3> cgi server

static void fastcgi_svr_thfunc( dgn_thread_t th, void * arg );
static void scgi_svr_thfunc( dgn_thread_t th, void * arg );

enum {
	CGISVR_STATE_EMPTY = 0,
	CGISVR_STATE_RUNNING,
	CGISVR_STATE_STOPING,
};

struct cgisvr_t
{
	int type;
	volatile int state;
	dgn_sock_t svrsock;
	int thread_num;
	dgn_thread_t * tid;
	dgn_mutex_t mutex;
	dgn_cgi_callback_func_t cb_func;
};

dgn_cgisvr_t dgn_cgisvr_create( int cgi_type, dgn_cgi_callback_func_t cb_func, const char * host, int port, int thread_num )
{
	if( cgi_type != DGN_CGISVR_TYPE_SCGI && cgi_type != DGN_CGISVR_TYPE_FASTCGI )
		return NULL;

	if( thread_num <= 0 || thread_num > 100000 )
		return NULL;

	dgn_sock_t sock = dgn_sock_tcp_svr( host, port );
	if( sock == NULL )
		return NULL;

	struct cgisvr_t * cs = (struct cgisvr_t *)malloc( sizeof(*cs) );
	cs->type = cgi_type;
	cs->state = CGISVR_STATE_EMPTY;
	cs->svrsock = sock;
	cs->thread_num = thread_num;
	cs->tid = NULL;
	cs->mutex = dgn_mutex_create();
	cs->cb_func = cb_func;
	return cs;
}

int dgn_cgisvr_start( dgn_cgisvr_t cgisvr )
{
	struct cgisvr_t * cs = (struct cgisvr_t *)cgisvr;
	if( cs->tid != NULL )
		return 0;

	cs->tid = (dgn_thread_t *)malloc( sizeof(dgn_thread_t) * cs->thread_num );
	memset( cs->tid, 0, sizeof(dgn_thread_t) * cs->thread_num );
	cs->state = CGISVR_STATE_RUNNING;
	int i;
	for( i = 0; i < cs->thread_num; i++ ) {
		if( cs->type == DGN_CGISVR_TYPE_FASTCGI )
			cs->tid[i] = dgn_thread_create( fastcgi_svr_thfunc, cs, NULL );
		else if( cs->type == DGN_CGISVR_TYPE_SCGI )
			cs->tid[i] = dgn_thread_create( scgi_svr_thfunc, cs, NULL );

		if( cs->tid[i] == NULL ) {
			break;
		}
	}
	if( i != cs->thread_num ) {
		dgn_cgisvr_stop( cs );
		return -1;
	}

	return 0;
}

int dgn_cgisvr_stop( dgn_cgisvr_t cgisvr )
{
	struct cgisvr_t * cs = (struct cgisvr_t *)cgisvr;
	if( cs->tid == NULL )
		return 0;

	cs->state = CGISVR_STATE_STOPING;
	int i;
	for( i = 0; i < cs->thread_num; i++ ) {
		if( cs->tid[i] != NULL )
			dgn_thread_wait_destroy( cs->tid[i] ), cs->tid[i] = NULL;
	}
	free( cs->tid ), cs->tid = NULL;
	cs->state = CGISVR_STATE_EMPTY;
	return 0;
}

int dgn_cgisvr_destroy( dgn_cgisvr_t cgisvr )
{
	struct cgisvr_t * cs = (struct cgisvr_t *)cgisvr;
	dgn_cgisvr_stop( cs );
	dgn_sock_close( cs->svrsock ), cs->svrsock = NULL;
	dgn_mutex_destroy( cs->mutex ), cs->mutex = NULL;
	free( cs );
	return 0;
}

///////////////////////////////////////////////
////	<4> fastcgi server

#define FCGI_VERSION_1           1
#define FCGI_NULL_REQUEST_ID     0
#define FCGI_HEADER_LEN          8

// FCGI record type
#define FCGI_BEGIN_REQUEST       1
#define FCGI_ABORT_REQUEST       2
#define FCGI_END_REQUEST         3
#define FCGI_PARAMS              4
#define FCGI_STDIN               5
#define FCGI_STDOUT              6
#define FCGI_STDERR              7
#define FCGI_DATA                8
#define FCGI_GET_VALUES          9
#define FCGI_GET_VALUES_RESULT  10
#define FCGI_UNKNOWN_TYPE       11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

// value name for GET_VALUES
#define FCGI_MAX_CONNS  "FCGI_MAX_CONNS"
#define FCGI_MAX_REQS   "FCGI_MAX_REQS"
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"

struct fcgi_head_t 
{
	uint8_t version;
	uint8_t type;
	uint8_t requestIdB1;
	uint8_t requestIdB0;
	uint8_t contentLengthB1;
	uint8_t contentLengthB0;
	uint8_t paddingLength;
	uint8_t reserved;
};

// begin request : flag
#define FCGI_KEEP_CONN           1  // BEGIN_REQUEST flag

// begin request : role
#define FCGI_RESPONDER  1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER     3

struct fcgi_body_begin_request_t
{
	uint8_t roleB1;
	uint8_t roleB0;
	uint8_t flags;
	uint8_t reserved[5];
};

// end request : protocolStatus
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN    1
#define FCGI_OVERLOADED       2
#define FCGI_UNKNOWN_ROLE     3

struct fcgi_body_end_request_t
{
	uint8_t appStatusB3;
	uint8_t appStatusB2;
	uint8_t appStatusB1;
	uint8_t appStatusB0;
	uint8_t protocolStatus;
	uint8_t reserved[3];
};

static inline int fastcgi_head_reqid( struct fcgi_head_t * head )
{
	return ( (int)head->requestIdB1 << 8 ) + head->requestIdB0;
}

static inline int fastcgi_head_content_len( struct fcgi_head_t * head )
{
	return ( (int)head->contentLengthB1 << 8 ) + head->contentLengthB0;
}

static int fastcgi_read_record( struct cgi_t * cgi, struct fcgi_head_t * head )
{
	// read head, read content, read padding
	if( dgn_sock_recv( cgi->clisock, (char *)head, sizeof(*head), sizeof(*head), NET_TIMEOUT_MS ) != sizeof(*head) )
		return -1;
	
	int cntlen = ( (int)head->contentLengthB1 << 8 ) + head->contentLengthB0;
	if( cgi->body_buflen - cgi->body_len < cntlen + head->paddingLength ) {
		int newlen = cgi->body_buflen * 2;
		if( newlen < 4 * 1024 )
			newlen = 4 * 1024;
		if( newlen - cgi->body_len < cntlen + head->paddingLength )
			newlen = cgi->body_len + cntlen + head->paddingLength;
		char * buf = (char *)malloc( newlen );
		memcpy( buf, cgi->body, cgi->body_len );
		free( cgi->body );
		cgi->body = buf;
		cgi->body_buflen = newlen;
	}
	if( dgn_sock_recv( cgi->clisock, cgi->body + cgi->body_len, cntlen + head->paddingLength, cntlen + head->paddingLength, NET_TIMEOUT_MS ) != cntlen + head->paddingLength ) {
		return -1;
	}
	cgi->body_len += cntlen;

	return 0;
}

static int parse_fastcgi_param( struct cgi_t * cgi )
{
	const char * p = cgi->body;
	unsigned int namelen, valuelen;

	while( p < cgi->body + cgi->body_len ) {
		namelen = *(unsigned char *)p++;
		if( (namelen >> 7) == 1 ) {
			namelen &= 0x7F;
			namelen = (namelen << 8) + *(unsigned char*)p++;
			namelen = (namelen << 8) + *(unsigned char*)p++;
			namelen = (namelen << 8) + *(unsigned char*)p++;
		}
		valuelen = *(unsigned char*)p++;
		if( (valuelen >> 7) == 1 ) {
			valuelen &= 0x7F;
			valuelen = (valuelen << 8) + *(unsigned char*)p++;
			valuelen = (valuelen << 8) + *(unsigned char*)p++;
			valuelen = (valuelen << 8) + *(unsigned char*)p++;
		}
		cgi_add_env( cgi, p, namelen, p + namelen, valuelen );
		p += namelen + valuelen;
	}
	return 0;
}

static int fastcgi_send_manager_record( struct cgi_t * cgi, int max )
{
	// TODO : don't read value, just return all we know
	char tmp_buf[16];
	snprintf( tmp_buf, sizeof(tmp_buf), "%d", max );

	char buf[256];
	struct fcgi_head_t * head = (struct fcgi_head_t *)buf;

	char * p = buf + sizeof(*head);
	int n1, n2;
	n1 = strlen( FCGI_MAX_CONNS );
	n2 = strlen( tmp_buf );
	*p++ = n1;
	*p++ = n2;
	memcpy( p, FCGI_MAX_CONNS, n1 );
	p += n1;
	memcpy( p, tmp_buf, n2 );
	p += n2;

	n1 = strlen( FCGI_MAX_REQS );
	n2 = strlen( tmp_buf );
	*p++ = n1;
	*p++ = n2;
	memcpy( p, FCGI_MAX_REQS, n1 );
	p += n1;
	memcpy( p, tmp_buf, n2 );
	p += n2;

	n1 = strlen( FCGI_MPXS_CONNS );
	n2 = 1;
	*p++ = n1;
	*p++ = n2;
	memcpy( p, FCGI_MPXS_CONNS, n1 );
	p += n1;
	*p++ = '0';

	int cntlen;
	cntlen = p - buf - sizeof(*head);

	head->version = FCGI_VERSION_1;
	head->type = FCGI_GET_VALUES_RESULT;
	head->requestIdB1 = 0;
	head->requestIdB0 = 0;
	head->contentLengthB1 = ((cntlen >> 8) & 0xFF);
	head->contentLengthB0 = (cntlen & 0xFF);
	head->paddingLength = 0;

	if( dgn_sock_send( cgi->clisock, buf, cntlen + sizeof(*head), NET_TIMEOUT_MS ) < 0 )
		return -1;

	return 0;
}

static int fastcgi_process( struct cgisvr_t * cs, struct cgi_t * cgi );
static int fastcgi_reply( struct cgi_t * cgi, const char * data, int len );

static void fastcgi_svr_thfunc( dgn_thread_t th, void * arg )
{
	struct cgisvr_t * cs = (struct cgisvr_t *)arg;
	dgn_sock_t clisock = NULL;

	while( cs->state == CGISVR_STATE_RUNNING )
	{
		dgn_mutex_lock( cs->mutex );
		clisock = dgn_sock_accept( cs->svrsock, NET_TIMEOUT_MS );
		dgn_mutex_unlock( cs->mutex );

		if( clisock == NULL )
			continue;

		while( 1 ) {
			struct cgi_t * c = cgi_create();
			c->clisock = clisock;
			c->reply_func = fastcgi_reply;
			int ret = fastcgi_process( cs, c );
			cgi_destroy( c ), c = NULL;
			if( ret < 0 )
				break;
		}
		dgn_sock_close( clisock ), clisock = NULL;
	}

	return;
}

static int fastcgi_process( struct cgisvr_t * cs, struct cgi_t * cgi )
{
	// manager record or begin request
	struct fcgi_head_t head;
	int flags;
	int ret_evt = 0;

	while( 1 ) {
		ret_evt = 0;
		dgn_sock_poll( cgi->clisock, DGN_POLLIN, &ret_evt, NET_TIMEOUT_MS );
		if( ret_evt == 0 ) {
			if( cs->state != CGISVR_STATE_RUNNING )
				return -1;
			continue;
		}

		if( fastcgi_read_record( cgi, &head ) < 0 ) {
			return -1;
		}
		if( head.type == FCGI_GET_VALUES ) {
			if( fastcgi_send_manager_record( cgi, cs->thread_num ) < 0 )
				return -1;
		}
		else if( head.type == FCGI_BEGIN_REQUEST ) {
			cgi->reqid = fastcgi_head_reqid( &head );
			struct fcgi_body_begin_request_t * body = (struct fcgi_body_begin_request_t *)cgi->body;
			flags = body->flags;
			break;
		}
		else {
			return -1;
		}
	}

	// param
	cgi->body_len = 0;
	while( 1 ) {
		if( fastcgi_read_record( cgi, &head ) < 0 )
			return -1;
		if( head.type != FCGI_PARAMS || fastcgi_head_reqid( &head ) != cgi->reqid ) {
			return -1;
		}
		if( fastcgi_head_content_len( &head ) == 0 )
			break;
	}
	parse_fastcgi_param( cgi );

	// stdin
	cgi->body_len = 0;
	while( 1 ) {
		if( fastcgi_read_record( cgi, &head ) < 0 )
			return -1;
		if( head.type != FCGI_STDIN || fastcgi_head_reqid( &head) != cgi->reqid ) {
			return -1;
		}
		if( fastcgi_head_content_len( &head ) == 0 )
			break;
	}

	// callback
	( * cs->cb_func )( cgi );
	dgn_cgi_reply_body( cgi, NULL, 0 );  // force finish header

	// send last stdout and end_request
	head.version = FCGI_VERSION_1;
	head.type = FCGI_STDOUT;
	head.requestIdB1 = ((cgi->reqid >> 8) & 0xFF);
	head.requestIdB0 = (cgi->reqid & 0xFF);
	head.contentLengthB1 = 0;
	head.contentLengthB0 = 0;
	head.paddingLength = 0;

	if( dgn_sock_send( cgi->clisock, (char *)&head, sizeof(head), NET_TIMEOUT_MS ) != sizeof(head) )
		return -1;

	struct fcgi_body_end_request_t body;
	memset( &body, 0, sizeof(body) );
	body.appStatusB3 = 0;
	body.appStatusB2 = 0;
	body.appStatusB1 = 0;
	body.appStatusB0 = 0;
	body.protocolStatus = FCGI_REQUEST_COMPLETE;

	head.type = FCGI_END_REQUEST;
	head.contentLengthB1 = 0;
	head.contentLengthB0 = sizeof(body);

	if( dgn_sock_send( cgi->clisock, (char *)&head, sizeof(head), NET_TIMEOUT_MS ) != sizeof(head) )
		return -1;
	if( dgn_sock_send( cgi->clisock, (char *)&body, sizeof(body), NET_TIMEOUT_MS ) != sizeof(body) )
		return -1;

	if( (flags & FCGI_KEEP_CONN) == 0 )
		return -1;

	return 0;
}

static int fastcgi_reply( struct cgi_t * cgi, const char * data, int len )
{
	if( len <= 0 )
		return -1;

	int num = 0;
	struct fcgi_head_t head;
	memset( &head, 0, sizeof(head) );
	head.version = FCGI_VERSION_1;
	head.type = FCGI_STDOUT;
	head.requestIdB1 = ((cgi->reqid >> 8) & 0xFF);
	head.requestIdB0 = (cgi->reqid & 0xFF);
	head.contentLengthB1 = 0;
	head.contentLengthB0 = 0;
	head.paddingLength = 0;

	while( num < len ) {
		int count = len - num;
		if( count > 65500 )
			count = 65500;
		head.contentLengthB1 = ((count >> 8) & 0xFF);
		head.contentLengthB0 = (count & 0xFF);

		if( dgn_sock_send( cgi->clisock, (char *)&head, sizeof(head), NET_TIMEOUT_MS ) != sizeof(head) )
			return -1;
		if( dgn_sock_send( cgi->clisock, data + num, count, NET_TIMEOUT_MS ) != count )
			return -1;
		num += count;
	}

	return num;
}

//////////////////////////////////////////////
////	<5> scgi server

static int scgi_process( struct cgisvr_t * cs, struct cgi_t * cgi );
static int scgi_reply( struct cgi_t * cgi, const char * data, int len );

static void scgi_svr_thfunc( dgn_thread_t th, void * arg )
{
	struct cgisvr_t * cs = (struct cgisvr_t *)arg;
	dgn_sock_t clisock = NULL;

	while( cs->state == CGISVR_STATE_RUNNING )
	{
		dgn_mutex_lock( cs->mutex );
		clisock = dgn_sock_accept( cs->svrsock, NET_TIMEOUT_MS );
		dgn_mutex_unlock( cs->mutex );

		if( clisock == NULL )
			continue;

		struct cgi_t * c = cgi_create();
		c->clisock = clisock;
		c->reply_func = scgi_reply;
		scgi_process( cs, c );
		cgi_destroy( c ), c = NULL;
		dgn_sock_close( clisock ), clisock = NULL;
	}

	return;
}

static int scgi_process( struct cgisvr_t * cs, struct cgi_t * cgi )
{
	char tmp_buf[16];
	if( dgn_sock_recv( cgi->clisock, tmp_buf, sizeof(tmp_buf), sizeof(tmp_buf), NET_TIMEOUT_MS ) != sizeof(tmp_buf) )
		return -1;
	const char * p = strchr( tmp_buf, ':' );
	if( p == NULL )
		return -1;
	int slen = atoi( tmp_buf );
	if( slen > 16 * 1024 * 1024 )
		return -1;

	// read param
	cgi->body_len = 0;
	if( cgi->body_buflen < slen + 1 ) {
		int newlen = slen + 1;
		if( newlen < 4 * 1024 )
			newlen = 4 * 1024;
		char * buf = (char *)malloc( newlen );
		free( cgi->body );
		cgi->body = buf;
		cgi->body_buflen = newlen;
	}
	int n = p - tmp_buf + 1; // skip [len] :
	memcpy( cgi->body + cgi->body_len, tmp_buf + n, 16 - n );
	if( dgn_sock_recv( cgi->clisock, cgi->body + 16 - n, slen - (16 - n), slen - (16 - n), NET_TIMEOUT_MS ) != slen - (16 - n) )
		return -1;
	cgi->body[slen + 1] = '\0';
	cgi->body_len = slen;

	// parse param
	p = cgi->body;
	int n1, n2;
	while( p - cgi->body < slen ) {
		n1 = strlen( p );
		n2 = strlen( p + n1 + 1 );
		cgi_add_env( cgi, p, n1, p + n1 + 1, n2 );
		p += n1 + 1 + n2 + 1;
	}

	// read content
	cgi->body_len = 0;
	int clen = 0;
	p = dgn_cgi_get_env( cgi, "CONTENT_LENGTH" );
	if( p != NULL )
		clen = atoi( p );
	if( clen > 16 * 1024 * 1024 )
		return -1;
	if( cgi->body_buflen < clen + 1 ) {
		int newlen = clen + 1;
		char * buf = (char *)malloc( newlen );
		free( cgi->body );
		cgi->body = buf;
		cgi->body_buflen = newlen;
	}
	if( dgn_sock_recv( cgi->clisock, cgi->body, clen, clen, NET_TIMEOUT_MS ) != clen )
		return -1;
	cgi->body_len = clen;

	// callback
	( * cs->cb_func )( cgi );
	dgn_cgi_reply_body( cgi, NULL, 0 );  // force finish header

	return 0;
}

static int scgi_reply( struct cgi_t * cgi, const char * data, int len )
{
	if( dgn_sock_send( cgi->clisock, data, len, NET_TIMEOUT_MS ) != len )
		return -1;
	// printf( "reply [%s] len %d\n", data, len );
	return len;
}

